﻿/*
Copyright © 2010 SectorPoint, Inc.

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
using System;
using System.IO;
using System.Text;

namespace SectorPoint.Net.Mail
{
	/// <summary>
	/// Provides the underlying stream of data for a POP3 connection.
	/// </summary>
	internal class Pop3Stream : System.IO.Stream
	{
		#region Constructor
		/// <summary>
		/// Creates a new instance of the SectorPoint.Net.Mail.Pop3Stream class for the specified System.IO.Stream.
		/// </summary>
		/// <param name="stream">The System.IO.Stream that the SectorPoint.Net.Mail.Pop3Stream will use to send and receive data.</param>
		internal Pop3Stream( Stream stream )
		{
			this.stream = stream;
		}
		#endregion

		#region Public Methods
		/// <summary>
		/// Reads a single-packet response from the POP3 server.
		/// </summary>
		/// <returns>The complete POP3 server response.</returns>
		public byte[] Read()
		{
			return Read( false, false, false );
		}

		/// <summary>
		/// Reads a multi-packet response from the POP3 server.
		/// </summary>
		/// <param name="multiLineResponse">True if the response should end with "\r\n.\r\n" (used for multi-line responses); otherwise, false.</param>
		/// <param name="trimStatusIndicator">If true, trims the status indicator line (if a good status message is received). If true, and a good status is not recevied, null is returned.</param>
		/// <param name="trimTerminator">If true, trims the message terminator ("\r\n.\r\n").</param>
		/// <returns>The POP3 server response.</returns>
		public byte[] Read(bool multiLineResponse, bool trimStatusIndicator, bool trimTerminator)
		{
			const int m_chunkSize = 1024;
			byte[] content = null;
			int totalBytesRead = 0;

			for (int i = 1; true; i++)
			{
				byte[] buffer = new byte[m_chunkSize];
				int bytesRead = 0;
				bytesRead = stream.Read( buffer, 0, m_chunkSize );

				totalBytesRead += bytesRead;

				if (bytesRead == m_chunkSize)
				{
					if (content != null)
					{
						byte[] newContent = new byte[totalBytesRead];
						Buffer.BlockCopy( content, 0, newContent, 0, content.Length );
						Buffer.BlockCopy( buffer, 0, newContent, content.Length, bytesRead );
						content = newContent;
					}
					else
						content = buffer;
					continue;
				}

				// This is the last chunk for this packet
				if (content != null)
				{
					byte[] newContent = new byte[totalBytesRead];
					Buffer.BlockCopy( content, 0, newContent, 0, content.Length );
					Buffer.BlockCopy( buffer, 0, newContent, content.Length, bytesRead );
					content = newContent;
				}
				else
					content = buffer;

				if (multiLineResponse)
				{
					if (content[totalBytesRead - 1] != 0xA || content[totalBytesRead - 2] != 0xD || content[totalBytesRead - 3] != 0x2E || content[totalBytesRead - 4] != 0xA || content[totalBytesRead - 5] != 0xD)
						continue;

					if (trimTerminator)
					{
						totalBytesRead = totalBytesRead - 5;
						byte[] newContent = new byte[totalBytesRead];
						Buffer.BlockCopy( content, 0, newContent, 0, totalBytesRead );
						content = newContent;
					}
				}
				break;
			}

			// Trim the status indicator line (i.e. "+OK POP3 server ready\r\n")
			if (trimStatusIndicator)
			{
				if (content[0] == 0x2b)
				{
					int i = 1;
					while (content[i] != 0xA)
						i++;

					totalBytesRead = totalBytesRead - i;
					byte[] newContent = new byte[totalBytesRead];
					Buffer.BlockCopy( content, i, newContent, 0, totalBytesRead );
					content = newContent;
				}
				else
					return null;
			}

			return content;
		}
		#endregion

		#region Private Fields
		/// <summary>
		/// Underlying stream of data for communicating to the POP3 server. Can be assumed to be either System.Net.Sockets.NetworkStream or System.Net.Security.SslStream.
		/// </summary>
		Stream stream;
		#endregion

		#region System.IO.Stream Implementation
		public override bool CanRead
		{
			get { return this.stream.CanRead; }
		}

		public override bool CanSeek
		{
			get { return this.stream.CanSeek; }
		}

		public override bool CanWrite
		{
			get { return this.stream.CanWrite; }
		}

		public override void Flush()
		{
			this.stream.Flush();
		}

		public override long Length
		{
			get { return this.stream.Length; }
		}

		public override long Position
		{
			get
			{
				return this.stream.Position;
			}
			set
			{
				this.stream.Position = value;
			}
		}

		public override int Read( byte[] buffer, int offset, int count )
		{
			return this.stream.Read( buffer, offset, count );
		}

		public override long Seek( long offset, System.IO.SeekOrigin origin )
		{
			return this.stream.Seek( offset, origin );
		}

		public override void SetLength( long value )
		{
			this.stream.SetLength( value );
		}

		public override void Write( byte[] buffer, int offset, int count )
		{
			this.stream.Write( buffer, offset, count );
		}
		#endregion
	}
}
